/*
 * Copyright (c) 2018, Texas Instruments Incorporated
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * *  Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * *  Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * *  Neither the name of Texas Instruments Incorporated nor the names of
 *    its contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
/* Standard includes */
#include <stdlib.h>
#include <stdint.h>

#include "otauser.h"
#include "ota/CdnClient.h"
#include "ota/OtaArchive.h"
#include "ota/OtaLib.h"
#include "ota/OtaHttpClient.h"
#include "ota/OtaJson.h"

OtaLib_t *pOtaLib;

int16_t OTA_init(OTA_runningMode runningMode, OTA_memBlock* pMemBlock, OTA_eventHandler eventHandler)
{
    _SlOtaLibTrace(("OTA_init: sizeof CdnClient=%d, sizeof OtaArchive=%d\r\n", sizeof(pOtaLib->CdnClient), sizeof(pOtaLib->OtaArchive)));
    _SlOtaLibTrace(("OTA_init: sizeof OtaLib_t=%d, sizeof OTA_memBlock=%d\r\n", sizeof(OtaLib_t), sizeof(OTA_memBlock)));
    if (sizeof(OtaLib_t) > sizeof(OTA_memBlock))
    {
        _SlOtaLibTrace(("OTA_init: ERROR buffer size OtaLib_t=%d < OTA_memBlock=%d\r\n", sizeof(OtaLib_t), sizeof(OTA_memBlock)));
        return OTA_INIT_ERROR;
    }
    pOtaLib = (OtaLib_t *)pMemBlock;

    memset(pOtaLib, 0, sizeof(OtaLib_t));
    pOtaLib->State = OTA_STATE_IDLE;

    _SlOtaLibTrace(("OTA_init: OTA lib version = %s\r\n", OTA_LIB_VERSION));

    CdnClient_Init(&pOtaLib->CdnClient, pOtaLib->NetBuf);
    OtaArchive_init(&pOtaLib->OtaArchive);

    /* init statistics */
    return OTA_STATUS_OK;
}

uint8_t *_ExtractFileVersion(uint8_t *pFileName, uint32_t FileNameSize)
{
    uint8_t *pVersionFileName = pFileName;

    /* VENDOR_DIR cannot be empty - the EXTLIB_OTA_SET_OPT_VENDOR_ID will fail first */

    /* minimum 2 slashes - in poll mode, filename must be "/VENDOR_DIR/file.tar", in push mode there are no slashaes */
    while (pVersionFileName != NULL)
    {
        pVersionFileName = (uint8_t *)Str_FindChar(pFileName, '/', FileNameSize);
        if (pVersionFileName != NULL)
        {
            /* go after the slash */
            pFileName = pVersionFileName+1;
        }
    }

    return pFileName;
}

void _OtaCleanToIdle(OtaLib_t *pOtaLib)
{
    /*_SlOtaLibTrace(("_OtaCleanToIdle: close OTA client and CDN client and back to IDLE\r\n");*/
    CdnClient_CloseServer(&pOtaLib->CdnClient);
    CdnClient_CloseFileServer(&pOtaLib->CdnClient);

    pOtaLib->pOtaFileName = NULL;
    pOtaLib->State = OTA_STATE_IDLE;
    /* don't clean consecutive errors */
}

int16_t OTA_set(OTA_option option, int32_t optionLen, uint8_t *pOptionVal, int32_t flags)
{
    int16_t Status = OTA_STATUS_OK;

    switch (option)
    {
        case EXTLIB_OTA_SET_OPT_SERVER_INFO:
            memcpy(&pOtaLib->OtaServerInfo, pOptionVal, sizeof(Ota_optServerInfo));
            break;

        case EXTLIB_OTA_SET_OPT_VENDOR_ID:
            if (strlen((const char *)pOptionVal) >= MAX_VENDIR_DIR_SIZE)
            {
                return OTA_OPT_ERROR_VENDOR_DIR_SIZE;
            }
            strcpy((char *)pOtaLib->VendorDir, (const char *)pOptionVal);
            break;

        case EXTLIB_OTA_SET_OPT_ACCEPT_UPDATE:
            /* do nothing, the OTA is already on process and not in IDLE state */
            break;

        case EXTLIB_OTA_SET_OPT_DECLINE_UPDATE:

            /* check if after OTA_STATE_CHECK_ARCHIVE_NEW_UPDATE state */
            if (pOtaLib->State != OTA_STATE_REQ_FILE_URL)
            {
                _SlOtaLibTrace(("OTA_set: ERROR EXTLIB_OTA_SET_OPT_DECLINE_UPDATE in wrong state = %d\r\n", pOtaLib->State));
                return OTA_OPT_ERROR_WRONG_STATE;
            }

            /* close all connection from last OTA updates and back to IDLE */
            pOtaLib->ConsecutiveOtaErrors = 0;
            _OtaCleanToIdle(pOtaLib);
            break;

        case EXTLIB_OTA_SET_OPT_IMAGE_COMMIT:

            Status = OtaArchive_commit();
            if (Status < 0)
            {
                _SlOtaLibTrace(("OTA_set: ERROR OtaArchive_Commit, status=%d\r\n", Status));
                return OTA_OPT_ERROR_COMMIT;
            }


            break;

        case EXTLIB_OTA_SET_OPT_IMAGE_ROLLBACK:

            Status = OtaArchive_rollback();
            if (Status < 0)
            {
                if (Status == SL_ERROR_FS_BUNDLE_NOT_IN_CORRECT_STATE)
                {
                    _SlOtaLibTrace(("OTA_set: ERROR OtaArchive_Rollback, status=%d - ignored SL_ERROR_FS_BUNDLE_NOT_IN_CORRECT_STATE\r\n", Status));
                    /* Ignore error calling rollback in the incorrect state */
                    return OTA_STATUS_OK;
                }
                _SlOtaLibTrace(("OTA_set: ERROR OtaArchive_Rollback, status=%d\r\n", Status));
                return OTA_OPT_ERROR_ROLLBACK;
            }
            break;

        default:
            _SlOtaLibTrace(("OTA_set: option %ld is not implemented\r\n", option));
            return OTA_OPT_ERROR_OPTION_CODE;
    }

    return Status;
}

int16_t OTA_get(OTA_option option, int32_t *pOptionLen, uint8_t *pOptionVal)
{
	Ota_optVersionsInfo *pVersionsInfo;
    int16_t Status = OTA_STATUS_OK;

    switch (option)
    {
        case EXTLIB_OTA_GET_OPT_IS_ACTIVE:
            *(int32_t *)pOptionVal = (pOtaLib->State != OTA_STATE_IDLE);
            *pOptionLen = sizeof(int32_t);
            break;

        case EXTLIB_OTA_GET_OPT_VERSIONS:

            pVersionsInfo = (Ota_optVersionsInfo *)pOptionVal;
            /* get the current version from ota.dat file */
            OtaArchive_getCurrentVersion(pVersionsInfo->CurrentVersion);
            /* extract the new version from OtaFileName if exists */
            if(pOtaLib->pOtaFileName == NULL)
            {
                /* can be if the OTA not in the right state and still didn't read the file name */
                /* OTA file not exists (no upadte or state before reading the ota new update */
                memset(pVersionsInfo->NewVersion, '0', VERSION_STR_SIZE);
            }
            else
            {
                uint8_t *pVersionFileName = _ExtractFileVersion(pOtaLib->pOtaFileName, MAX_CDN_FILE_NAME_SIZE);
                /* extract version from file */
                memcpy(pVersionsInfo->NewVersion, pVersionFileName, VERSION_STR_SIZE);
            }
            pVersionsInfo->NewVersion[VERSION_STR_SIZE] = 0; /* null terminated string */
            break;

        case EXTLIB_OTA_GET_OPT_IS_PENDING_COMMIT:

            *(int32_t *)pOptionVal = OtaArchive_getPendingCommit();
            break;

        default:
            _SlOtaLibTrace(("OTA_get: option %ld is not implemented\r\n", option));
            Status = OTA_OPT_ERROR_OPTION_CODE;
    }

    return Status;
}

#define BACK_TO_IDLE 0x1

int16_t _OtaCheckConsecutiveErrors(OtaLib_t *pOtaLib, int16_t OtaStatus, int32_t Options)
{
    int16_t Status;

    if (++pOtaLib->ConsecutiveOtaErrors >= MAX_CONSECUTIVE_OTA_ERRORS)
    {
        _SlOtaLibTrace(("\n_OtaCheckConsecutiveErrors: ConsecutiveOtaErrors=%d/%d, MAX_CONSECUTIVE_OTA_ERRORS!!! \r\n", pOtaLib->ConsecutiveOtaErrors, MAX_CONSECUTIVE_OTA_ERRORS));
        pOtaLib->ConsecutiveOtaErrors = 0;
        Status = OTA_RUN_ERROR_CONSECUTIVE_OTA_ERRORS;
    }
    else
    {
        _SlOtaLibTrace(("\n_OtaCheckConsecutiveErrors: ConsecutiveOtaErrors=%d/%d, return only WARNNING\r\n", pOtaLib->ConsecutiveOtaErrors, MAX_CONSECUTIVE_OTA_ERRORS));
        Status = OtaStatus;
    }

    if (Options & BACK_TO_IDLE)
    {
        pOtaLib->State = OTA_STATE_IDLE;
        _OtaCleanToIdle(pOtaLib);
    }

    return Status;
}


